package douyu_live

import (
	"crypto/md5"
	"encoding/hex"
	"strconv"

	"encoding/json"
	"fmt"
	"io"
	"iptv_server/v3/v2_src/utils"
	"net/http"
	"net/http/cookiejar"
	"regexp"
	"strings"
	"time"

	"github.com/dop251/goja"
)

const (
	did  string = "10000000000000000000000000001501" // 设备id默认值.
	cdn  string = "ws-h5"                            //cdn: 主线路ws-h5、备用线路tct-h5
	rate int    = 2                                  //rate: 1流畅；2高清；3超清；4蓝光4M；0蓝光8M或10M
)

var (
	regCompiled_roomid  *regexp.Regexp = nil
	regCompiled_js      *regexp.Regexp = nil
	regCompiled_js2     *regexp.Regexp = nil
	regCompiled_js_sub  *regexp.Regexp = nil
	regCompiled_js_sign *regexp.Regexp = nil

	cachedRoomIDs map[int64]int64
)

func init() {
	initVar()
	cachedRoomIDs = make(map[int64]int64)
}

func initVar() {
	regCompiled_roomid = regexp.MustCompile(`rid":(\d{1,8}),"vipId`)
	regCompiled_js = regexp.MustCompile(`(vdwdae325w_64we[\s\S]*function ub98484234[\s\S]*?)function`)
	regCompiled_js_sub = regexp.MustCompile(`eval.*?;}`)
	regCompiled_js2 = regexp.MustCompile(`v=(\d+)`)
	regCompiled_js_sign = regexp.MustCompile(`return rt;}\);?`)
}

type Params struct {
	Code int `json:"code"`
	Data struct {
		Live_status int `json:"live_status"`
		Room_id     int `json:"room_id"`
	}
}

type Result struct {
	Data struct {
		Rtmp_url  string `json:"rtmp_url"`
		Rtmp_live string `json:"rtmp_live"`
	} `json:"data"`
}

type douyu struct {
	id int64

	t10    string // 时间戳 秒
	client *http.Client
	url    string
	params string
}

func (d *douyu) roomid() string {
	return strconv.FormatInt(d.id, 10)
}

func (d *douyu) post(req *http.Request) (live_url string) {
	res, err := d.client.Do(req)
	if err != nil {
		return
	}
	utils.Logger.Debugf("POST:: res.StatusCode: %v", res.StatusCode)
	defer res.Body.Close()

	result := Result{}

	bs, _ := io.ReadAll(res.Body)
	utils.Logger.Debugf("bs: %v", string(bs))

	err = json.Unmarshal(bs, &result)
	utils.Logger.Debugf("decode: err: %v", err)
	utils.Logger.Debugf("result: %v", result)

	if len(result.Data.Rtmp_url) == 0 || len(result.Data.Rtmp_live) == 0 {
		return ""
	}
	live_url = fmt.Sprintf("%s/%s", result.Data.Rtmp_url, result.Data.Rtmp_live)
	return
}

func (d *douyu) getVideoUrl() (live_url string) {
	utils.Logger.Infof("POST:: url: %v", d.url)
	utils.Logger.Infof("POST:: params: %v", d.params)

	r := strings.NewReader(d.params) //发送数据
	req, err := http.NewRequest(http.MethodPost, d.url, r)
	if err != nil {
		return
	}
	req.Header.Add("User-Agent", utils.UA)
	req.Header.Add(utils.HeaderKeyContentType, utils.ContentTypeXWWWFormUrlencoded)

	return d.post(req)
}

func (d *douyu) initState() bool {
	req, err := http.NewRequest(http.MethodGet, "https://www.douyu.com/"+d.roomid(), nil)
	if err != nil {
		return false
	}
	req.Header.Add("User-Agent", utils.UA)

	res, err := d.client.Do(req)
	if err != nil {
		utils.Logger.Debugf("douyuLive initState d.client.Do(req) err=%v", err)
		return false
	}
	defer res.Body.Close()

	// if res.StatusCode != http.StatusOK {
	// 	utils.Logger.Debugf("douyuLive initState res.StatusCode != http.StatusOK, StatusCode=%v", res.StatusCode)
	// 	return
	// }

	if bs, err := io.ReadAll(res.Body); err != nil {
		return false
	} else {
		//version:sign version
		//js_res : js script
		version, js_res := getJsStr(bs)
		if len(version) <= 0 {
			return false
		}

		d.url, d.params = get_url_and_params(d.id, d.t10, version, js_res)
		return true
	}
}

func (d *douyu) init(roomid int64) bool {
	if roomid <= 0 {
		return false
	}
	realID := getRealRoomID(roomid)
	if realID <= 0 {
		realID = roomid
	}

	d.id = realID
	d.t10 = fmt.Sprintf("%d", time.Now().Unix())
	// cookiejar.Jar
	jar, _ := cookiejar.New(nil)
	d.client = &http.Client{
		Jar:     jar,
		Timeout: time.Second * 10,
	}
	return true
	// return getWithState(client, str_room_id, t10)
}

func getRealRoomID(roomid int64) int64 {
	if v, ok := cachedRoomIDs[roomid]; ok {
		return v
	} else {
		url := fmt.Sprintf("https://m.douyu.com/%d", roomid)
		req, err := http.NewRequest(http.MethodGet, url, nil)
		if err != nil {
			return roomid // fallback
		}
		client0 := &http.Client{
			Timeout: time.Second * 10,
		}
		res, err := client0.Do(req)
		if err != nil {
			return roomid
		}
		defer res.Body.Close()

		if bs, err := io.ReadAll(res.Body); err == nil {
			if room_id, ok := findRoomID(bs); ok {
				//房间号通常为1~8位纯数字，浏览器地址栏中看到的房间号不一定是真实rid.
				return room_id
			}
		}
	}
	return roomid
}

// func GetUrlRandom(roomid int) (url string) {
// 	// const strings = [2]string{"hello", "world"}
// 	// urls := GetVideoUrls(833792)
// 	urls := GetVideoUrls(roomid)
// 	url = ""
// 	if urls == nil {
// 		println("[warning] get no url")
// 	} else {
// 		fmt.Printf("urls: %v\n", urls)
// 		// rand.Seed(time.Now().UnixNano())
// 		n := rand.Intn(len(urls))
// 		for _, url = range urls {
// 			if n == 0 {
// 				return
// 			}
// 			n--
// 		}
// 	}
// 	return

// 	// url_keys := [3]string{"http_hls_fmp4_avc", "http_hls_ts_avc", "http_stream_flv_avc"}
// 	// fmt.Printf("url_keys: %v\n", url_keys)
// }

func getJsStr(bs []byte) (string, string) {
	// bss:=regCompiled.Find(bs,-1)
	// bss:=regCompiled.FindAll(bs,-1)
	// bss:=regCompiled.FindSubmatch(s,-1) //[0]整个正则匹配的, [1]括号内匹配的. [n]第n个括号?
	// bss:=regCompiled.FindAllSubmatch(s,-1) //FindSubmatch的All版本, (FindSubmatch 匹配到一个即结束)

	// bss:=regCompiled.FindAllString(s,-1) // []byte版->string版
	// bss:=regCompiled.FindAllStringSubmatch(s,-1)

	bss := regCompiled_js.FindSubmatch(bs)
	if len(bss) <= 1 {
		utils.Logger.Debug("no match : regCompiled_js.FindSubmatch(bs)")
		return "", ""
	}

	bs = bss[1]

	fmt.Printf("getJsStr: %v\n", "x3")
	bs = regCompiled_js_sub.ReplaceAll(bs, []byte("strc;}"))
	f, _ := utils.GetCallable(string(bs), "ub98484234")
	if f == nil {
		utils.Logger.Debug("js GetCallable failed : function ub98484234(...){}")
		return "", ""
	}

	jsValue, err := f(goja.Undefined())
	if err != nil {
		utils.Logger.Warnf("return err function ub98484234(...){},%v", err)
		return "", ""
	}
	utils.Logger.Debugf("function ub98484234(...){} value=%v", jsValue)

	// res:=jsValue.Export().(string)
	res, ok := jsValue.Export().(string)
	if !ok {
		utils.Logger.Debug("function ub98484234(...){} ,jsValue.Export().(string) failed")
		return "", ""
	}

	vs := regCompiled_js2.FindStringSubmatch(res)
	if len(vs) <= 1 {
		utils.Logger.Debug("no match ,regCompiled_js2.FindStringSubmatch(res)")
		return "", ""
	}
	// vs[1] : sign version
	// res : js script
	utils.Logger.Debugf("bs1 : sign version=%v", vs[1])
	return vs[1], res
}

func evalMd5(s string) string {
	b := md5.Sum([]byte(s))
	h := hex.EncodeToString(b[:])
	return h
}

func findRoomID(bs []byte) (int64, bool) {
	bss := regCompiled_roomid.FindSubmatch(bs)
	if len(bss) > 1 {
		id := string(bss[1])
		if i, err := strconv.ParseInt(id, 10, 64); err == nil {
			return i, true
		}
	}
	return -1, false
}

func get_url_and_params(roomid int64, t10, version, js string) (string, string) {
	id := strconv.FormatInt(roomid, 10)
	//md5
	rb := evalMd5(fmt.Sprintf("%s%s%s%s", id, did, t10, version))
	utils.Logger.Debugf("roomid=%s,md5=%s", id, rb)

	func_sign := regCompiled_js_sign.ReplaceAllString(js, `return rt;}`)
	func_sign = strings.ReplaceAll(func_sign, "(function (", "function sign(")
	func_sign = strings.ReplaceAll(func_sign, "CryptoJS.MD5(cb).toString()", `"`+rb+`"`)

	// params := evalJS(func_sign,"sign", id, did, t10)

	f, vm := utils.GetCallable(func_sign, "sign")
	if f == nil {
		utils.Logger.Debugf("get func_sign failed, roomid=%s", id)
		return "", ""
	}

	jsValue, err := f(goja.Undefined(), vm.ToValue(id), vm.ToValue(did), vm.ToValue(t10))
	if err != nil {
		utils.Logger.Debugf("eval func_sign error, roomid=%s, err=%v", id, version)
		return "", ""
	}

	//params : 没有单独计算sign, 直接导出了 params==格式()"application/x-www-form-urlencoded")
	params, ok := jsValue.Export().(string)
	if !ok {
		utils.Logger.Debugf("eval func_sign export failed, roomid=%s", id)
		return "", ""
	}

	params = fmt.Sprintf("%s&cdn=%s&rate=%d", params, cdn, rate)
	url := fmt.Sprintf("https://www.douyu.com/lapi/live/getH5Play/%s", id)
	return url, params

}
